﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Buffers;
using System.IO;
using System.Text.Json;

namespace SnapObjects.Data
{
    internal class SystJsonStreamReader
    {
        private long _readTotal;
        private long _bytesConsumed;

        private bool _isInitReader;
        private bool _isFinalBlock;

        private JsonReaderState _preReaderState;
        private ReadOnlySequence<byte> _sequence;

        private readonly Stream _stream;
        private readonly int _bufferSize = 1024;
        private readonly SysJsonStreamLinkedSegment _linkedSegment;

        public SystJsonStreamReader(Stream stream)
        {
            _stream = stream;
            _readTotal = 0;

            if (stream.Length < Int32.MaxValue)
            {
                _bufferSize = (int)stream.Length;
            }

            _isInitReader = false;
            _linkedSegment = new SysJsonStreamLinkedSegment(_bufferSize);

            _bytesConsumed = 0;
        }

        public bool TryRead(out Utf8JsonReader reader, bool autoRead)
        {
            reader = this.GetReader();

            if (autoRead)
            {
                while (!reader.Read())
                {
                    if (_isFinalBlock)
                    {
                        return false;
                    }

                    reader = this.MoveNext();
                }

                this.RecordReaderState(ref reader);
            }

            return true;
        }

        public bool TryPreRead(ref Utf8JsonReader reader)
        {
            while (!reader.Read())
            {
                if (_isFinalBlock)
                {
                    return false;
                }

                reader = this.MoveNext();
            }

            return true;
        }

        public bool Skip()
        {
            var reader = this.GetReader();

            while (!reader.TrySkip())
            {
                if (_isFinalBlock)
                {
                    return false;
                }

                reader = this.MoveNext();
            }

            this.RecordReaderState(ref reader);

            return true;
        }

        public void HoldSegment()
        {
            _linkedSegment.HoldSegment(_bytesConsumed);
        }

        public Utf8JsonReader GetReader()
        {
            Utf8JsonReader reader;

            if (!_isInitReader)
            {
                reader = this.MoveNext();

                _isInitReader = true;
            }
            else
            {
                reader = new Utf8JsonReader(_sequence, _isFinalBlock, _preReaderState);
            }

            return reader;
        }

        public object GetValue(ref Utf8JsonReader reader)
        {
            object value = null;

            switch (reader.TokenType)
            {
                case JsonTokenType.String:
                {
                    value = reader.GetString();
                }
                break;
                case JsonTokenType.Number:
                {
                    if (reader.TryGetInt32(out var intValue))
                    {
                        value = intValue;
                    }
                    else if (reader.TryGetInt64(out var longValue))
                    {
                        value = longValue;
                    }
                    else if (reader.TryGetDecimal(out var decValue))
                    {
                        value = decValue;
                    }
                    else if (reader.TryGetDouble(out var doubleValue))
                    {
                        value = doubleValue;
                    }
                }
                break;
                case JsonTokenType.True:
                case JsonTokenType.False:
                {
                    value = reader.GetBoolean();
                }
                break;
                case JsonTokenType.Null:
                {
                }
                break;
            }

            return value;
        }

        public void ReadAsRaw(ref Utf8JsonReader reader)
        {
            var depth = 1;

            while (depth > 0)
            {
                if (!this.TryRead(out reader, true))
                {
                    return;
                }

                if (reader.TokenType == JsonTokenType.StartObject ||
                    reader.TokenType == JsonTokenType.StartArray)
                {
                    depth++;
                }
                else if (reader.TokenType == JsonTokenType.EndObject ||
                         reader.TokenType == JsonTokenType.EndArray)
                {
                    depth--;
                }
            }
        }

        public ReadOnlySequence<byte> GetHoldSegmentSequence()
        {
            return _linkedSegment.GetHoldSegmentSequence(_bytesConsumed);
        }

        public void CancelHoldSegments()
        {
            _linkedSegment.CancelHoldSegments(_bytesConsumed);
        }

        private void RecordReaderState(ref Utf8JsonReader reader)
        {
            _preReaderState = reader.CurrentState;

            _bytesConsumed += reader.BytesConsumed;

            _sequence = _sequence.Slice(reader.BytesConsumed);

        }
        private Utf8JsonReader MoveNext()
        {
            this.LoadSegment();

            var reader = new Utf8JsonReader(_sequence, _isFinalBlock, _preReaderState);

            return reader;
        }

        private void LoadSegment()
        {
            if (_isFinalBlock)
            {
                return;
            }

            _linkedSegment.UpdateStartIndex(_bytesConsumed);

            var validatedSegment = _linkedSegment.GetValidatedSegment(_bytesConsumed);

            var readCount = validatedSegment.Load(_stream);

            _readTotal += readCount;

            _isFinalBlock = _readTotal == _stream.Length;

            if (_linkedSegment.FirstSegment.StartIndex == _bufferSize)
            {
                _sequence = new ReadOnlySequence<byte>(_linkedSegment.LastSegment.Memory);
            }
            else
            {
                _sequence = new ReadOnlySequence<byte>(
                    _linkedSegment.FirstSegment,
                    _linkedSegment.FirstSegment.StartIndex,
                    _linkedSegment.LastSegment,
                    _linkedSegment.LastSegment.BufferCount);
            }
        }
    }
}